home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
130 MIDI Tool Box
/
130 MIDI Tool Box.iso
/
ptmid
/
ptmidsav.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-01-08
|
11KB
|
344 lines
/*
* ptmidsav.c: MOD-format saver module for ptmid. Takes a structure
* representing a tune and creates a file which contains that tune.
*
* Author: Andrew Scott (c)opyright 1994
*
* Date: 17/11/1993 ver 0.0
* 8/1/1994 ver 0.1
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ptmid.h"
/*
* PutblanksPfile: Outputs as many null characters to a given file as
* is desired (max. 131). Inelegant solution.
*/
void PutblanksPfile(FILE *pfile, int cch)
{
if (cch)
fwrite("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0", 1, cch, pfile);
}
/*
* PutPfileSz: Outputs a string to a given file and pads it to the correct
* length by outputting null characters if necessary.
*/
void PutPfileSz(FILE *pfile, Sz szT, int cch)
{
while (*szT && cch) {
putc(*(szT++), pfile);
cch--;
}
PutblanksPfile(pfile, cch);
}
/*
* WritePfile: Writes division information (sample, period, effect) to
* given file in the standard Protracker format.
*/
void WritePfile(FILE *pfile, unsigned bSam, unsigned wPer, unsigned wEff)
{
putc((bSam & 0xF0) | (wPer >> 8), pfile);
putc(wPer & 0xFF, pfile);
putc((bSam & 0x0F) << 4 | (wEff >> 8), pfile);
putc(wEff & 0xFF, pfile);
}
/*
* PeiNextPtune: Given a pointer to a tune, will start using it if not
* already using one. Will return next event in quanta, else NULL.
* If it gets to the end of the tune, will set flag to false and will
* wait to be given a new tune.
*/
EI *PeiNextPtune(Tune *ptuneMain, int *pf)
{
static Tune *ptune = NULL;
static unsigned long quant;
EI *pei;
if (NULL == ptune) { /** If first time called **/
if (NULL == ptuneMain) /** If no tune given, quit **/
return NULL;
*pf = 1;
quant = 0;
ptune = ptuneMain; /** Initialize tune pointer to start of tune **/
} else /** Else **/
quant++; /** Advance along tune 1 quantize fraction **/
if (quant < ptune->count) /** If haven't reached next event **/
return NULL; /** return nothing-happening **/
pei = ptune->pei; /** Otherwise note next event **/
if ((ptune = ptune->ptune) == NULL) /** If no more events **/
*pf = 0; /** register end of tune **/
return pei; /** Return that event **/
}
/*
* Convqpm: Converts a given qpm number into a double tempo thingy.
*/
void Convqpm(unsigned qpm, int rgbTempo[2], int ticks)
{
if (792 / ticks <= qpm && 6144 / ticks - 1 >= qpm) {
rgbTempo[0] = ticks; /** If can use current ticks/div, do so **/
rgbTempo[1] = qpm * ticks / 24;
} else if (33 > qpm) /** Else if qpm is very small **/
if (26 > qpm) { /** approximate it **/
rgbTempo[0] = 31;
rgbTempo[1] = 33;
} else {
rgbTempo[0] = 31;
rgbTempo[1] = qpm * 31 / 24;
}
else if (6120 <= qpm) { /** Else if qpm is very big **/
rgbTempo[0] = 1; /** approximate it too **/
rgbTempo[1] = 255;
} else { /** Else look for closest fraction **/
unsigned iT;
iT = (qpm + 254) / 255; /**### try best fit fraction later ###**/
rgbTempo[0] = 24 / iT;
rgbTempo[1] = qpm / iT;
}
}
/*
* PutpatternsPtunePfile: Given an output file and a tune, will output the
* tune as standard Protracker patterns. wPtchan determines number of
* channels per division. wPatmax determines maximum number of patterns
* to write before terminating.
*/
int PutpatternsPtunePfile(Tune *ptune, FILE *pfile)
{
int iT, iT2, wPat = 0, cDiv, ipw, ipwMax, fGoing, ini;
unsigned pwLen[MAXPTCHAN] = {0,0,0,0,0,0,0,0}, pwNote[3 * MAXPTCHAN];
unsigned rgbTempo[2] = {6,125};
long wNewtempo = 120;
EI *pei;
NI *pni;
pei = PeiNextPtune(ptune, &fGoing); /** Get first event **/
ipw = wPtchan;
ipwMax = wPtchan * 3;
for (wPat = 0; fGoing; wPat++) { /** Loop until told to stop **/
if (wPat == wPatmax) { /** If pattern limit reached, stop **/
fprintf(stderr, "ptmid: Warning -- Pattern limit %d reached. "
"Aborting!\n", wPatmax);
break;
}
for (cDiv = 64; cDiv--; ) { /** For each division in a pattern **/
memset(pwNote, 0, ipwMax * sizeof(unsigned)); /** Clear next notes **/
for (iT = wPtchan; iT--; ) /** With any currently playing notes **/
if (pwLen[iT])
if (0 == --pwLen[iT]) { /** Check if just stopped **/
pwNote[iT2 = iT * 3] = MAXPTSAMPS; /** Yes.. store quiet command **/
pwNote[iT2 + 1] = 0;
pwNote[iT2 + 2] = 0xC00;
}
if (fGoing) { /** If still going in the tune **/
for (; NULL != pei; pei = pei->peiNext) /** For each event at this position **/
if (pei->cni) { /** If note-event **/
pni = pei->pni;
for (ini = pei->cni; ini--; ) /** For each note **/
if (-1 != pni[ini].inst) { /** which is valid **/
iT = ipwMax - 1; /*** Find channel to stick note ***/
for (; 0 < iT && 0xC00 != pwNote[iT]; iT -= 3);
if (0 > iT) {
for (iT = ipw; iT--; )
if (0 == pwLen[iT])
break;
if (0 > iT) {
for (iT = wPtchan; iT-- > ipw; )
if (0 == pwLen[iT])
break;
if (0 > iT) {
iT2 = pei->effect / 2;
for (iT = ipw; iT--; )
if (iT2 <= pwLen[iT])
break;
if (0 > iT) {
for (iT = wPtchan; iT-- > ipw; )
if (iT2 <= pwLen[iT])
break;
if (0 > iT)
continue;
}
}
}
if (--ipw < 0)
ipw = wPtchan - 1;
iT = iT * 3 + 2;
}
pwNote[iT - 2] = pni[ini].inst + 1; /*** Store note ***/
pwNote[iT - 1] = pni[ini].pitch;
if (pni[ini].vol != rgmsDecided[pni[ini].inst].bDefvol)
pwNote[iT] = 0xC00 + pni[ini].vol;
else
pwNote[iT] = 0;
pwLen[iT / 3] = pei->effect;
}
} else /** Else store new tempo **/
wNewtempo = pei->effect;
pei = PeiNextPtune(NULL, &fGoing);
}
if (0 != wNewtempo) { /** If need to set a tempo this division **/
int rgbNew[2];
Convqpm(wNewtempo, rgbNew, rgbTempo[0]); /** Find protracker equivalent **/
if (rgbNew[0] != rgbTempo[0] || rgbNew[1] != rgbTempo[1]) {
for (iT = ipwMax - 1; iT > 0; iT -= 3) /** Find a channel for it **/
if (0 == pwNote[iT])
break;
if (iT < 0) { /** If no channel.. damn. Have to replace something **/
unsigned bMin = -1, bTest;
for (iT2 = ipwMax - 1; iT2 > 0; iT2 -= 3) {
bTest = abs((pwNote[iT2] & 0xFF) - rgmsDecided[pwNote[iT2 - 2] - 1].bDefvol);
if (bTest < bMin) {
bMin = bTest;
iT = iT2; /** Find best thing to replace **/
}
}
}
if (rgbNew[0] != rgbTempo[0])
if (rgbNew[1] != rgbTempo[1]) { /** If need two places **/
pwNote[iT] = 0xFFF;
for (iT2 = ipwMax - 1; iT2 > 0; iT2 -= 3) /** Find a channel **/
if (0 == pwNote[iT2])
break;
if (iT2 < 0) { /** No channels.. use different new tempo **/
iT2 = rgbNew[1] * rgbTempo[0] / rgbNew[0];
if (255 < iT2)
if (280 < iT2)
iT2 = 0;
else
iT2 = 255;
else if (33 > iT2)
if (30 > iT2)
iT2 = 33;
else
iT2 = 0;
if (0 != iT2) { /** If we can allow for ~10% bpm variance **/
pwNote[iT] = 0xF00 + iT2; /** then use it **/
rgbTempo[1] = iT2;
} else { /** Else try to find a ticks value **/
iT2 = rgbNew[0] * rgbTempo[1] / rgbNew[1];
if (0 == iT2)
iT2 = 1;
else if (32 < iT2)
iT2 = 32;
pwNote[iT] = 0xF00 + iT2; /** and use it **/
rgbTempo[0] = iT2;
}
} else {
pwNote[iT] = 0xF00 + rgbNew[0]; /** Store 2 value tempo **/
rgbTempo[0] = rgbNew[0];
pwNote[iT2] = 0xF00 + rgbNew[1];
rgbTempo[1] = rgbNew[1];
}
} else {
pwNote[iT] = 0xF00 + rgbNew[0]; /** Store just ticks/div **/
rgbTempo[0] = rgbNew[0];
}
else {
pwNote[iT] = 0xF00 + rgbNew[1]; /** Store just bpm **/
rgbTempo[1] = rgbNew[1];
}
}
wNewtempo = 0;
}
for (iT = ipwMax - 1; iT > 0; iT -= 3) /** Write division to file **/
WritePfile(pfile, pwNote[iT - 2], pwNote[iT - 1], pwNote[iT]);
}
}
return wPat; /** Return number of patterns written **/
}
/*
* SavePtunePfile: Given an output file and a tune, will output the tune to
* the file using standard Protracker MODule format.
*/
void SavePtunePfile(Tune *ptune, FILE *pfile)
{
int iT, cPatterns, ch, cSamps = 0;
long Patternpos, Samplepos, cch;
SI **ppsi;
FILE *pfileSamp;
char *pch;
PutPfileSz(pfile, szTitle, 20); /** Write title **/
for (iT = 0; iT < MAXPTSAMPS; iT++) { /** Write sample info **/
PutPfileSz(pfile, rgmsDecided[iT].szName, 22);
if (0 == rgmsDecided[iT].cMix)
PutblanksPfile(pfile, 8);
else {
cSamps++;
putw(0, pfile);
putc(0, pfile);
putc(rgmsDecided[iT].bDefvol, pfile);
ppsi = rgmsDecided[iT].ppsiMix;
putc((*ppsi)->wLppos >> 8, pfile);
putc((*ppsi)->wLppos & 0xFF, pfile);
putc((*ppsi)->wLplen >> 8, pfile);
putc((*ppsi)->wLplen & 0xFF, pfile);
}
}
Patternpos = ftell(pfile); /** Store start of pattern # table **/
PutblanksPfile(pfile, 130); /** Write it as blanks **/
fwrite(szId, 4, 1, pfile); /** Write protracker id **/
cPatterns = PutpatternsPtunePfile(ptune, pfile); /** Write patterns **/
Samplepos = ftell(pfile); /** Store start of samples data **/
fseek(pfile, Patternpos, SEEK_SET); /** Go back to pattern # table **/
putc(cPatterns, pfile);
putc(127, pfile);
for (iT = 0; iT < cPatterns; ) /** Write pattern numbers **/
putc(iT++, pfile);
pch = (char *) malloc(512);
if (!fQuiet)
printf("Writing %d samples: ", cSamps);
for (iT = 0; iT < MAXPTSAMPS; iT++) /** Write samples **/
if (0 < rgmsDecided[iT].cMix) {
fseek(pfile, Samplepos, SEEK_SET);
ppsi = rgmsDecided[iT].ppsiMix;
pfileSamp = fopen((*ppsi)->fnSample, "rb");
if (NULL == pch)
while ((ch = getc(pfileSamp)) != EOF)
putc(ch, pfile);
else
while (fwrite(pch, 1, fread(pch, 1, 512, pfileSamp), pfile) == 512);
cch = ftell(pfileSamp) / 2;
fclose(pfileSamp);
Samplepos = ftell(pfile);
fseek(pfile, 42 + 30 * iT, SEEK_SET);
putc(cch >> 8, pfile);
putc(cch & 0xFF, pfile);
if (!fQuiet)
printf(".");
}
if (!fQuiet)
printf("\n");
if (NULL != pch)
free(pch);
}